/******************************************************************************

    Copyright (C) 2006 Alessio Stalla (alessiostalla@gmail.com)

    This file is part of PrettyProlog.

    PrettyProlog is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    PrettyProlog is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with PrettyProlog; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    See the file COPYING for further details.

 *****************************************************************************/

package prettyprolog.engine;

import java.util.ArrayList;
import java.util.List;

import prettyprolog.engine.listeners.StackEvent;
import prettyprolog.engine.listeners.StackListener;

/**
 * The engine's stack. Besides the obvious push/pop operations, PrettyProlog's
 * Stack support StackListeners that capture push, pop, cut &amp; clear events.<br />
 * The Stack is not thread-safe (and it's not meant to be), its methods aren't
 * synchronized.
 */
public class Stack {

	private Frame stack = null;
	private int size = 0;

	/** Adds a Frame on top of this Stack. */
	public void push(Frame frame) {
		frame.setNext(stack);
		stack = frame;
		size++;
		dispatchPushEvent(frame);
	}

	private Frame remove_top() {
		Frame fr = null;
		if (stack != null) {
			fr = stack;
			stack = stack.getNext();
		}
		size--;
		return fr;
	}

	/**
	 * Removes a Frame from the top of the stack.
	 * 
	 * @return the removed Frame.
	 */
	public Frame pop() {
		Frame fr = remove_top();
		dispatchPopEvent(fr);
		return fr;
	}

	/**
	 * Removes a Frame from the top of the stack. It differs from the pop()
	 * method only because it signals a different event to the listeners, i.e.
	 * it calls frameCut() instead of framePopped().
	 * 
	 * @return the removed Frame.
	 */
	public Frame cut() {
		Frame fr = remove_top();
		dispatchCutEvent(fr);
		return fr;
	}

	/** @return the Frame at the given index. */
	public Frame get(int index) {
		Frame ptr = stack;
		for (int i = 0; i < index; i++) {
			if (ptr != null) {
				ptr = ptr.getNext();
			} else {
				throw new IndexOutOfBoundsException();
			}
		}
		return ptr;
	}

	/** @return the Frame at the top of the Stack. */
	public Frame top() {
		if (stack == null) {
			return null;
		}
		return get(0);
	}

	/** @return true if there are no Frames in this Stack, false otherwise. */
	public boolean isEmpty() {
		return stack == null;
	}

	/** @return the number of Frames in the Stack. */
	public int getSize() {
		return size;
	}

	/** Removes all the frames. */
	public void clear() {
		stack = null;
		size = 0;
		dispatchClearEvent();
	}

	// LISTENERS
	/** The List of StackListeners attached to this Stack. */
	protected List<StackListener> listeners = new ArrayList<StackListener>();

	/** Adds a listener to the listeners list. */
	public void addStackListener(StackListener listnr) {
		listeners.add(listnr);
	}

	/** Removes a listener from the list. */
	public void removeStackListener(StackListener listnr) {
		listeners.remove(listnr);
	}

	/** Signals to every listener that a frame has been popped from the stack. */
	protected void dispatchPopEvent(Frame fr) {
		StackEvent ev = new StackEvent(fr);
		for (StackListener listener : listeners) {
			listener.framePopped(ev);
		}
	}

	/** Signals to every listener that a frame has been cut from the stack. */
	protected void dispatchCutEvent(Frame fr) {
		StackEvent ev = new StackEvent(fr);
		for (StackListener listener : listeners) {
			listener.frameCut(ev);
		}
	}

	/** Signals to every listener that a frame has been pushed to the stack. */
	protected void dispatchPushEvent(Frame fr) {
		StackEvent ev = new StackEvent(fr);
		for (StackListener listener : listeners) {
			listener.framePushed(ev);
		}
	}

	/** Signals to every listener that the stack has been emptied. */
	protected void dispatchClearEvent() {
		StackEvent ev = new StackEvent(null);
		for (StackListener listener : listeners)
			listener.stackCleared(ev);
	}
}